Saltar al contenido principal

7.3.4 Relaciones e Integridad de Datos

Una base de datos no es solo un almacén de tablas aisladas. Su verdadero poder reside en la capacidad de relacionar información y asegurar que esos datos sean coherentes y fiables. Esto se conoce como Integridad Referencial.

En este punto, aprenderemos a conectar tablas y a poner "reglas de juego" para que la base de datos rechace automáticamente información incoherente.


1️⃣ Relaciones entre Tablas: Claves y Conexiones​

Para conectar dos tablas, usamos dos conceptos fundamentales:

🟩 Clave Primaria (Primary Key - PK)​

Es el "DNI" de cada fila. Identifica de forma única e inequívoca a un registro.

  • No puede repetirse.
  • No puede ser nula.

🟦 Clave Foránea (Foreign Key - FK)​

Es una columna en una tabla (hija) que apunta a la Clave Primaria de otra tabla (padre). Es el "enlace" que crea la relación.

-- Tabla Padre: Autores
CREATE TABLE autores (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nombre TEXT NOT NULL UNIQUE
);

-- Tabla Hija: Libros (cada libro pertenece a un autor)
CREATE TABLE libros (
id INTEGER PRIMARY KEY AUTOINCREMENT,
titulo TEXT NOT NULL,
autor_id INTEGER,
FOREIGN KEY (autor_id) REFERENCES autores(id)
);
¡Importante en SQLite!

Por defecto, SQLite no valida las claves foráneas por compatibilidad histórica. Debes activarlas manualmente al conectar:

conexion = sqlite3.connect("mi_base.db")
conexion.execute("PRAGMA foreign_keys = ON") # Imprescindible

2️⃣ Restricciones de Integridad​

Las restricciones son reglas que definimos en las columnas para evitar que entren datos "sucios" o incorrectos.

🟩 UNIQUE y NOT NULL​

  • NOT NULL: Obliga a que el campo tenga un valor.
  • UNIQUE: Impide que dos filas tengan el mismo valor en esa columna (ej: un email o un ISBN).

🟦 CHECK y DEFAULT​

  • CHECK (condición): Solo permite insertar datos que cumplan una regla lógica.
  • DEFAULT valor: Si no se indica nada, pone un valor por defecto.
CREATE TABLE usuarios (
id INTEGER PRIMARY KEY,
nombre TEXT NOT NULL,
email TEXT UNIQUE,
edad INTEGER CHECK(edad >= 18),
rol TEXT DEFAULT 'lector'
);

3️⃣ Acciones Referenciales: ¿Qué pasa si borro al "padre"?​

Cuando borras un registro que tiene "hijos" (datos que apuntan a él), ¿qué debe hacer la base de datos?

🟩 ON DELETE RESTRICT (Por defecto)​

Impide borrar al padre si tiene hijos. Primero debes borrar los hijos o reasignarlos. Es la opción más segura.

🟦 ON DELETE CASCADE​

Si borras al padre, borra automáticamente todos sus hijos. Útil para "limpiezas automáticas" pero peligroso si no se tiene cuidado.

🟨 ON DELETE SET NULL​

Si borras al padre, los hijos se quedan, pero su referencia (FK) pasa a ser NULL.

CREATE TABLE libros (
...
FOREIGN KEY (autor_id) REFERENCES autores(id)
ON DELETE CASCADE -- Si borro un autor, borro todos sus libros
);

4️⃣ Manejo de Errores con IntegrityError​

Cuando intentamos romper una regla (insertar un duplicado en un campo UNIQUE o apuntar a un ID que no existe), Python lanzará una excepción específica.

import sqlite3

try:
with sqlite3.connect("biblioteca.db") as conn:
conn.execute("PRAGMA foreign_keys = ON")
# Intento de insertar un libro con un autor_id que NO existe
conn.execute("INSERT INTO libros (titulo, autor_id) VALUES (?, ?)", ("Inexistente", 999))
except sqlite3.IntegrityError as e:
print(f"❌ Error de integridad detectado: {e}")

✅ Buenas prácticas en Diseño Relacional​

resumen profesional
  • Normaliza: Si ves que repites mucho un texto (ej: nombres de autores), sácalo a una tabla propia y usa IDs. Ahorra espacio y evita errores de escritura.
  • Activa siempre el PRAGMA: No confíes en que SQLite lo haga solo. Hazlo nada más conectar.
  • Usa Nombres Claros: Llama a tus claves foráneas con el formato tablaPadre_id (ej: autor_id) para que sea fácil seguir el rastro.
  • Defensa en la Base: Es mejor que la base de datos lance un error (CHECK, UNIQUE) a validar todo mil veces en Python. Delega la seguridad en el esquema.

🧪 Ejercicios prácticos – Misión: La Cripta de Datos (Parte 4)​

La Cripta de Datos está creciendo tanto que el sistema actual es un caos. El bibliotecario jefe quiere normalizar la base de datos: separar los libros de sus autores y de sus categorías para evitar duplicados.

🟢 Fase 1 – Reestructuración Masiva​

🟩 Ejercicio 1 – El Archivo de Sabios​

Crea una tabla autores (con id PK y nombre UNIQUE). Crea también una tabla categorias (con id PK y nombre UNIQUE).

🟦 Ejercicio 2 – Libros Conectados​

Modifica el esquema de la tabla libros (puedes borrarla y crearla de nuevo) para que no guarde el nombre del autor ni la categoría como texto, sino como autor_id y categoria_id. Define las Foreign Keys correspondientes y activa el PRAGMA foreign_keys = ON.

🟡 Fase 2 – Blindaje de Datos​

🟨 Ejercicio 3 – Reglas de la Orden​

Añade las siguientes restricciones a la tabla libros:

  1. El titulo debe ser único (UNIQUE).
  2. El número de paginas debe ser siempre superior a 0 (CHECK).
  3. El autor_id no puede ser nulo (NOT NULL).

🟧 Ejercicio 4 – El Detector de Errores​

Crea un script que intente realizar las siguientes acciones ilegales y capture el IntegrityError mostrando un mensaje personalizado para cada uno:

  1. Insertar un autor que ya existe.
  2. Insertar un libro con 0 páginas.
  3. Insertar un libro apuntando a un autor_id que no existe.

🔴 Fase 3 – Gestión de Despedidas​

🟥 Ejercicio 5 – Borrado en Cascada​

Configura la relación para que, si un autor es expulsado de la biblioteca secreta y su registro es borrado, todos sus libros desaparezcan automáticamente con él (ON DELETE CASCADE). Prueba el funcionamiento insertando un autor, varios libros suyos y luego borrando solo al autor.

🟪 Ejercicio 6 – La Gran Consulta (Preview)​

Realiza una consulta que muestre por pantalla: "El libro [Título] fue escrito por el autor [Nombre]". (Pista: necesitarás unir ambas tablas en tu SELECT comparando libros.autor_id == autores.id)